﻿using System;
using System.Globalization;
using System.Reflection;
using System.Xml.Linq;
using Microsoft.Office.OneNote.Interop;

namespace Microsoft.Office.OneNote
{
    internal static class OneNoteObjectFactory
    {
        // Create with identity
        
        private static readonly Type[] IdCtorTypes = new Type[] { typeof(OneNoteObjectId) };
        private static readonly ParameterModifier[] IdCtorModifiers = new ParameterModifier[0];

        public static OneNoteObjectWithIdentity CreateWithIdentity(XName name, OneNoteObjectId id)
        {
            Type type = OneNoteSchema.GetObjectType(name);

            return CreateWithIdentity(type, id);
        }

        public static T CreateWithIdentity<T>(OneNoteObjectId id) where T : OneNoteObjectWithIdentity
        {
            return (T)CreateWithIdentity(typeof(T), id);
        }

        public static OneNoteObjectWithIdentity CreateWithIdentity(Type type, OneNoteObjectId id)
        {
            ConstructorInfo ctor = GetIdConstructor(type);

            if (ctor == null)
            {
                string message = string.Format(CultureInfo.CurrentUICulture,
                    ErrorMessages.TypeDoesNotHaveAnObjectIdCtor, type);

                throw new ArgumentException(message);
            }

            return (OneNoteObjectWithIdentity)ctor.Invoke(new object[] { id });
        }

        // Create anonymous

        public static OneNoteObject Create(XName name)
        {
            Type type = OneNoteSchema.GetObjectType(name);

            if (type != null)
            {
                return Create(type);
            }

            return null;
        }

        public static OneNoteObject Create(Type type)
        {
            if (typeof(OneNoteObject).IsAssignableFrom(type))
            {
                return (OneNoteObject)Activator.CreateInstance(type, true);
            }

            throw new ArgumentException();
        }

        public static T Create<T>() where T : OneNoteObject, new()
        {
            return new T();
        }

        public static CreateHierarchyType GetHierarchyType(Type type)
        {
            var attribute = type.GetAttribute<OneNoteHierarchyTypeAttribute>();

            if (attribute != null)
            {
                return attribute.HierarchyType;
            }

            return CreateHierarchyType.None;
        }

        /// <summary>
        /// Creates a new instance of the same type as source argument.
        /// If the source object is an <see cref="T:OneNoteObjectWithIdentity"/>,
        /// the ID is copied.
        /// </summary>
        /// <typeparam name="TObject">The type of the object.</typeparam>
        /// <param name="source">The source object.</param>
        /// <returns>New uninitialized object of the same type and with the same ID.</returns>
        public static TObject PrepareClone<TObject>(TObject source) where TObject : OneNoteObject
        {
            OneNoteObjectWithIdentity sourceWithId = (source as OneNoteObjectWithIdentity);
            Type resultType = source.GetType();

            if (sourceWithId != null)
            {
                return (TObject)(OneNoteObject)CreateWithIdentity(resultType, sourceWithId.ObjectId);
            }

            return (TObject)Create(resultType);
        }

        private static ConstructorInfo GetIdConstructor(Type type)
        {
            return type.GetConstructor(
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
                null, IdCtorTypes, IdCtorModifiers);
        }
    }
}
